package com.cskaoyan.javase.object._6clone;

/**
 * clone方法
 * 1.方法的声明
 * clone方法的方法声明为：
 * protected native Object clone() throws CloneNotSupportedException;
 * 这里需要注意的地方有：
 *      1.首先注意访问权限，它的访问权限是protected。这意味着：
 *          一般来说，只能在子类当中，创建子类自身对象才能够调用该方法
 *          （方法调用位置，肯定不是同包）。
 *          但是方法的重写可以修改方法的访问权限修饰符
 *
 *      2.让一个类自身克隆自身，一般都没有多大意义，所以建议在子类中重写方法访问权限。
 *      3.它是一个本地native方法，没有方法体。
 *      （依赖本地方法实现创建对象，不同于new对象）
 *      4.throws CloneNotSupportedException是方法抛出异常的声明，这里我们先不管，后面异常的章节会讲解。
 *
 * 2.方法的作用
 * 这个方法的名字，其实已经很明显的告诉了你它的作用：
 * 克隆，生物学意义上的克隆，是用生物的体细胞，经过无性繁殖，得到相同基因型后代的过程。
 *
 * Java当中的克隆方法clone()有类似的作用，当你在程序中有这种需求，即希望：
 *      1.得到一个和原先对象，完全独立的新对象。
 *      2.成员仍和原先对象一致。
 * 有这种做法时，普通的做法就是再new一个一模一样的
 * 但学习clone方法后，你就可以用该方法来做这个事情了。
 * clone方法是一种完全独立于new对象创建对象的方式,所以它很重要.
 * 所以Object类当中的clone方法默认实现
 * 就是得到一个独立的,和原先对象成员一致的新对象。
 *
 *
 * 3.clone方法的使用流程
 *      a.(必做)如果不是要在自身类当中克隆自己(意义不大)
 *          那么为了能够在外部其他类中调用某个类的clone方法
 *          需要重写访问权限修饰符
 *          clone方法的使用使用的第一步就是要突破访问权限的限制
 *          根据使用场景,决定是否重写为public或者仍然保持protected
 *
 *      b.(选做,建议做)
 *      Object类当中的clone方法实现不会修改对象的数据类型
 *      所以可以选择重写返回值类型, 这样便于方法调用
 *
 *      c.(必须做)
 *      要想对某个类实现克隆操作
 *      那么该类就必须实现接口"java.lang.Cloneable"
 *      如果不实现该接口,强行进行克隆操作
 *      就会报错,抛出异常:CloneNotSupportedException
 *
 * 注意事项/细节问题:
 * 1.clone方法是完全不同new对象的创建对象方式,它依赖于本地方法创建对象,它创建对象,不会执行构造器.
 * 它和new创建对象属于平行的关系.
 *
 * 2.Cloneable接口的问题
 * java.lang.Cloneable是一个空接口
 * 实现它不会新增任何成员,那么实现它的目的是什么呢?
 * 空接口的作用又是什么呢?
 * 空接口的作用是标记类,允许该类做一些操作.
 * 像Cloneable这种没有声明定义任何成员的，一个空接口，它其实就起到一个标记的作用，称之为"标记接口"。
 * 被Cloneable标记的类是允许做克隆操作的，反之不允许。JDK中的标记接口，我们在后面还会见到。
 *
 * 3.上面我们已经说过了，在进行克隆操作时
 * 正常情况下，我们使用Object类当中的默实现就足够了,不需要重写实现。
 * 但假如你真的有需求，对于某个对象的引用x，JDK文档中也规定了一些重写的原则：
 * x.clone() != x 为 true
 * 克隆必须是一个新的独立的对象,不然就不是克隆了.
 *
 * x.clone().getClass() == x.getClass() 一般也为true
 * 克隆一般不会改变对象的类型,但是如果有需要,也不是不行.
 *
 * x.clone().equals(x) 一般情况下也为true
 * 这种为true是有前提是equals方法被重写了,而且是按照成员变量的取值重写的.
 *
 * 重写clone方法是非常少见的.
 *
 * 4.Object当中默认实现的clone方法是浅克隆的:
 *      a.如果成员变量是基本数据类型变量,那么clone方法会在新对象当中直接复制一份该成员变量
 *      克隆后的基本数据类型成员变量不会互相影响
 *
 *      b.如果成员变量是引用数据类型变量,对象中存储的是引用数据类型成员变量的引用,这时clone方法会将该引用复制一份到新的对象当中,拷贝引用和原先对象中的引用仍然会指向同一个对象
 *      这样这两个引用的操作就会互相影响
 *      所以浅克隆对于对象中的引用数据类型成员变量,不是一个完全的克隆,得到的克隆对象和原先对象不是完全独立的
 *
 * 以上就引申出了深度克隆(深克隆,深拷贝)的概念:
 * 深度克隆会得到一个完全独立的新对象
 * 那么深度克隆怎么做呢?
 * 1.深度克隆是在浅克隆基础上完成的,没有浅克隆也做不了深度克隆
 * 2.将对象中引用数据类型成员变量引用所指向的对象克隆一份
 * 3.然后将克隆引用指向克隆后的对象
 *
 *
 * @since 16:44
 * @author wuguidong@cskaoyan.onaliyun.com
 */
public class Demo {
    public static void main(String[] args) throws CloneNotSupportedException {
        /*Student s = new Student(18, 100);
        // 如果不重写访问权限,就会报以下错误
        // 'clone()' has protected access in 'java.lang.Object'
        Student cloneStu = s.clone();
        System.out.println(s == cloneStu);
        System.out.println(s.equals(cloneStu));
        System.out.println(s.hashCode());
        System.out.println(cloneStu.hashCode());*/
        System.out.println("-----------------------");
        Student stu = new Student(28, 200, new Star(18));
        Student cStu = stu.clone();
        System.out.println(stu == cStu);
        stu.age = 38;
        System.out.println(cStu.age);
        System.out.println("----------------");
        stu.s.age = 8;
        System.out.println(cStu.s.age);
    }
}

interface MyCloneable extends Cloneable {
}

class Student implements MyCloneable {
    int age;
    double score;
    Star s;

    public Student(int age, double score) {
        System.out.println("构造器执行了!");
        this.age = age;
        this.score = score;
    }

    public Student(int age, double score, Star s) {
        this.age = age;
        this.score = score;
        this.s = s;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        return Double.compare(student.score, score) == 0;
    }

    @Override
    public int hashCode() {
        int result;
        long temp;
        result = age;
        temp = Double.doubleToLongBits(score);
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        return result;
    }

    // 因为Object类的默认实现clone不会改变对象的数据类型
    @Override
    protected Student clone() throws CloneNotSupportedException {
        // 1.深度克隆是在浅克隆基础上完成的,所以仍然要调用super.clone()
        Student cloneStu = (Student) super.clone();
        // 2.将对象中s引用指向的对象克隆一份,这里要克隆Star类的对象
        Star cloneStar = s.clone();
        // 3.然后将克隆引用指向克隆后的对象
        cloneStu.s = cloneStar;
        return cloneStu;
    }
}

class Star implements Cloneable {
    int age;

    public Star(int age) {
        this.age = age;
    }

    @Override
    protected Star clone() throws CloneNotSupportedException {
        return (Star) super.clone();
    }
}